package com.yugao.fintech.antelope.infra.idgenerator;

import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import com.yugao.fintech.framework.idgenerator.config.IdGeneratorProperties;
import com.yugao.fintech.framework.lock.constant.LockBeanName;
import com.yugao.fintech.framework.lock.manager.ILockManager;
import com.yugao.fintech.framework.lock.model.ILock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 自动注册workId
 */
@Slf4j
@Component
public class AutoRegisterWorkerId {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired(name = LockBeanName.REDISSON_SERVICE)
    private ILockManager lockManager;

    /**
     * 可以适当增加该值, 同时 {@link IdGeneratorProperties#getWorkerIdBitLength()} } 位数也要增加
     */
    static final Long MAX_VALUE = 63L;

    static final String INDEX_KEY = "idGen:workerId:index";
    /**
     * arg1: 候选值
     */
    static final String VALUE_KEY = "idGen:workerId:value:%s";

    /**
     * 刷新workId占用时间, 要求调用方每间隔10s调用一次
     *
     * @param workId 服务端生成的workId
     * @return true 刷新成功
     */
    public boolean refresh(String workId) {
        if (StringUtils.isEmpty(workId)) {
            throw new BizException("workId [{}] 不能为空", workId);
        }
        String workIdKey = String.format(VALUE_KEY, workId);
        redisTemplate.opsForValue().set(workIdKey, workId, 20, TimeUnit.SECONDS);
        return true;
    }

    /**
     * 获取成功返回 > -1数, 需要加分布式锁
     *
     * @return -1 获取失败
     */
    public Short getNextWorkId() {
        ILock lock = lockManager.getLock("lock:idGen:workId");
        boolean tryLock = lock.tryLock(10, TimeUnit.SECONDS);
        if (!tryLock) {
            throw new BizException("id生成器workId锁被其他服务占用");
        }
        try {
            for (int i = 0; i < 10; i++) {
                // setup 从redis中获取自增值 (INDEX_KEY) 作为候选WorkerId
                Long readyWorkId = ObjectUtils.defaultIfNull(redisTemplate.opsForValue().increment(INDEX_KEY, 1), 1L);

                // setup 候选值值大于最大值(是), 大循环次数+1， 设置 (INDEX_KEY) = -1, 超过10次返回-1
                if (readyWorkId > MAX_VALUE) {
                    redisTemplate.opsForValue().decrement(INDEX_KEY, MAX_VALUE + 1);
                    log.warn("生成 workId [{}] 已经达到最大值, 开始重置并重新生成", readyWorkId);
                    continue;
                }

                // setup 候选值值大于最大值(否), 检查候选值 VALUE_KEY 是否被占用
                String readyWorkIdKey = String.format(VALUE_KEY, readyWorkId);

                // setup 候选值值被占用, 则重新从redis获取自增值
                if (Boolean.TRUE.equals(redisTemplate.hasKey(readyWorkIdKey))) {
                    log.warn("生成 workId [{}] 被占用, 开始获取下一个WorkId", readyWorkId);
                    continue;
                }

                // setup 候选值值没有被占用, 设置 VALUE_KEY 有效期为20s, 并返回workId
                redisTemplate.opsForValue().set(readyWorkIdKey, readyWorkId, 20, TimeUnit.SECONDS);
                log.info("生成 workId [{}] 成功", readyWorkId);
                return Short.parseShort(String.valueOf(readyWorkId));
            }

            // 最后需要调用方间隔10s, 刷新一下 workId 占用时间
            log.warn("workId 生成失败, 所有值都被占用, maxValue: {} ", MAX_VALUE);
            return -1;
        } finally {
            lock.unlock();
        }
    }
}
